﻿using UnityEngine;
using System.Threading;

/**
 * This class allows a Unity program to continually check for messages from a
 * serial device.
 *
 * It creates a Thread that communicates with the serial port and continually
 * polls the messages on the wire.
 * That Thread puts all the messages inside a Queue, and this SerialController
 * class polls that queue by means of invoking SerialThread.GetSerialMessage().
 *
 * The serial device must send its messages separated by a newline character.
 * Neither the SerialController nor the SerialThread perform any validation
 * on the integrity of the message. It's up to the one that makes sense of the
 * data.
 */
public class SerialController : MonoBehaviour
{
    [Tooltip("串口号")]
    public string portName = "COM3";

    [Tooltip("波特率")]
    public int baudRate = 9600;

    [Tooltip("引用一个场景对象，它将接收连接断开的事件和来自串行设备的消息")]
    public GameObject messageListener;

    [Tooltip("在串行通信出现错误或连接不成功后，我们应该等待多少毫秒")]
    public int reconnectionDelay = 1000;

    [Tooltip("队列中未读数据消息的最大数量新消息或旧消息(取决于“Drop old Message”配置)将被丢弃")]
    public int maxUnreadMessages = 1;

    [Tooltip("当队列满时，最好删除队列中最旧的消息，而不是新的传入消息。 如果您希望保留来自端口的最新消息，可以使用此选项")]
    public bool dropOldMessage;

    // 用于标记连接开始和结束的常量。
    // 当我比较这些字符串的引用时，
    // 您不可能从串行设备生成冲突消息，而不可能生成它们的内容。
    // 如果你从串行设备发送这些相同的字符串，在重构时它们会有不同的引用id。  
    public const string SERIAL_DEVICE_CONNECTED = "__Connected__";
    public const string SERIAL_DEVICE_DISCONNECTED = "__Disconnected__";

    // 对Thread和在其中运行的对象的内部引用。  
    protected Thread thread;
    protected SerialThreadLines serialThread;

    /// <summary>
    /// 每当SerialController游戏对象被激活时调用。 它创建一个新线程，试图连接到串行设备并开始从它读取数据。  
    /// </summary>
    void OnEnable()
    {
        serialThread = new SerialThreadLines(portName,
                                             baudRate,
                                             reconnectionDelay,
                                             maxUnreadMessages,
                                             dropOldMessage);
        thread = new Thread(new ThreadStart(serialThread.RunForever));
        thread.Start();
    }

    /// <summary>
    /// 当SerialController游戏对象被禁用时调用。 它停止并销毁正在从串行设备读取数据的线程。
    /// </summary>
    void OnDisable()
    {
        // 如果有用户定义的拆除函数，请在关闭底层COM端口之前执行它
        if (userDefinedTearDownFunction != null)
            userDefinedTearDownFunction();

        // serialThread引用不应该为空在，这一点上，除非一个异常发生在OnEnable()
        if (serialThread != null)
        {
            serialThread.RequestStop();
            serialThread = null;
        }

        // 无论如何，这个引用不应该是空的。  
        if (thread != null)
        {
            thread.Join();
            thread = null;
        }
    }

    // ------------------------------------------------------------------------
    // 从SerialThread对象保存的队列中轮询消息。 一旦消息被轮询，它就会从队列中移除。 有一些特殊的消息标记了与设备通信的开始/结束。  
    // ------------------------------------------------------------------------
    void Update()
    {
        // 如果用户喜欢轮询消息而不是通过SendMessage接收消息，那么消息侦听器应该为空。  
        if (messageListener == null)
            return;

        // 从队列中读取下一条消息
        string message = (string) serialThread.ReadMessage();
        if (message == null)
            return;

        // 检查该消息是否是普通数据或连接/断开事件。  
        if (ReferenceEquals(message, SERIAL_DEVICE_CONNECTED))
            messageListener.SendMessage("OnConnectionEvent", true);
        else if (ReferenceEquals(message, SERIAL_DEVICE_DISCONNECTED))
            messageListener.SendMessage("OnConnectionEvent", false);
        else
            messageListener.SendMessage("OnMessageArrived", message);
    }

    /// <summary>
    /// 从串行设备返回一个新的未读消息。 只有在不提供消息侦听器的情况下，才需要调用此函数。
    /// </summary>
    /// <returns></returns>
    public string ReadSerialMessage()
    {
        // 从队列中读取下一条消息
        return (string) serialThread.ReadMessage();
    }

    /// <summary>
    /// 将消息放入传出队列。 线程对象将在它认为合适的时候将消息发送给串行设备。
    /// </summary>
    /// <param name="message"></param>
    public void SendSerialMessage(string message)
    {
        serialThread.SendMessage(message);
    }

    /// <summary>
    /// 在Unity关闭COM端口之前执行一个用户定义的函数，所以用户可以可靠地向硬件发送一些拆除消息。
    /// </summary>
    public delegate void TearDownFunction();

    private TearDownFunction userDefinedTearDownFunction;

    public void SetTearDownFunction(TearDownFunction userFunction)
    {
        this.userDefinedTearDownFunction = userFunction;
    }

    public void setMessage()
    {
        SendSerialMessage("q");
    }
}
